package download

import (
	"changeme/backend/utils"
	"context"
	"fmt"
	"github.com/wailsapp/wails/v2/pkg/runtime"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

var wg sync.WaitGroup

type FileInfo struct {
	AcceptRange string
	FileSize    uint64
}

func (info *FileInfo) String() string {
	return fmt.Sprintf("AcceptRange => %s, FileSize => %s", info.AcceptRange, utils.FileSizeWithUint64(info.FileSize))
}

type Downloader struct {
	io.Reader
	Name    string
	Path    string
	Url     string
	Total   uint64
	Current uint64
	Speed   string
}

type SpeedOfProgress struct {
	Name    string
	Path    string
	Url     string
	Total   uint64
	Current uint64
	Speed   string
}

func (d *Downloader) Read(p []byte) (n int, err error) {
	n, err = d.Reader.Read(p)
	atomic.AddUint64(&d.Current, uint64(n))
	return
}

func RequestInfo(url string) *FileInfo {
	client := http.Client{Timeout: time.Second * 30}
	req, err := http.NewRequest(http.MethodHead, url, nil)
	if err != nil {
		panic(err)
	}

	resp, err := client.Do(req)
	if err != nil {
		panic(err)
	}

	fileSize, _ := strconv.ParseUint(resp.Header.Get("Content-Length"), 10, 64)

	return &FileInfo{
		AcceptRange: resp.Header.Get("Accept-Ranges"),
		FileSize:    fileSize,
	}
}

func Download(ctx context.Context, eventPrefix string, url string, path string) {
	events := fmt.Sprintf(eventPrefix, "progress")
	runtime.LogDebug(ctx, events)

	filePath, _ := filepath.Abs(path)
	info := RequestInfo(url)

	client := http.Client{}
	req, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		panic(err)
	}

	resp, err := client.Do(req)
	if err != nil {
		panic(err)
	}

	f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, os.ModePerm)
	defer func() {
		_ = f.Close()
	}()
	if err != nil {
		panic(err)
	}

	downloader := &Downloader{
		Reader: resp.Body,
		Name:   filepath.Base(path),
		Path:   filePath,
		Total:  info.FileSize,
	}

	wg.Add(1)
	go func() {
		defer wg.Done()
		var tmp uint64 = 0
		for {
			time.Sleep(1 * time.Second)

			now := atomic.LoadUint64(&downloader.Current)

			downloader.Speed = fmt.Sprintf("%s/s", utils.FileSizeWithUint64(now-tmp))

			runtime.EventsEmit(ctx, events, &SpeedOfProgress{
				Name:    downloader.Name,
				Path:    downloader.Path,
				Url:     downloader.Url,
				Total:   downloader.Total,
				Current: downloader.Current,
				Speed:   downloader.Speed,
			})

			if now >= downloader.Total {
				break
			}
			tmp = now
		}
		_ = f.Close()
	}()
	if _, err = io.Copy(f, downloader); err != nil {
		panic(err)
	}
	wg.Wait()
}

func AutoRetryWithDownload(ctx context.Context, eventPrefix string, url string, path string) (err error) {
	retry := 3
	for {
		startTime := time.Now()
		err := func() (err error) {
			defer func() {
				r := recover()
				if r != nil {
					err = r.(error)
				}
			}()
			Download(ctx, eventPrefix, url, path)
			return
		}()
		if err != nil {
			retry--
			runtime.LogDebugf(ctx, "第 %d 次重试, %s", 3-retry, url)
			if retry <= 0 {
				return err
			}
			continue
		}
		sumTime := time.Now().Sub(startTime)
		runtime.LogDebug(ctx, sumTime.String())
		err = nil
		break
	}
	return
}

type Response struct {
	Error error
}

const channel = "download"

func EventsHandler(ctx context.Context, optionalData ...interface{}) {
	url := optionalData[0].(string)
	path := optionalData[1].(string)

	eventPrefix := fmt.Sprintf("%s:%%s:%s", channel, path)

	path = strings.Replace(path, "$temp", os.TempDir(), 1)

	events := fmt.Sprintf(eventPrefix, "done")

	runtime.LogDebugf(ctx, "url: %+v, path: %+v", url, path)
	err := AutoRetryWithDownload(ctx, eventPrefix, url, path)

	runtime.LogDebug(ctx, events)
	if err != nil {
		runtime.LogErrorf(ctx, "%s", err)
		runtime.EventsEmit(ctx, events, Response{err})
	}

	if err != nil {
		runtime.LogErrorf(ctx, "%s", err)
		runtime.EventsEmit(ctx, events, Response{err})
	}

	runtime.EventsEmit(ctx, events, Response{nil})
}
